package com.yeskery.nut.webhook.controller;

import com.alibaba.fastjson2.JSONObject;
import com.yeskery.nut.annotation.bean.Autowired;
import com.yeskery.nut.annotation.environment.Value;
import com.yeskery.nut.annotation.web.Controller;
import com.yeskery.nut.annotation.web.GetMapping;
import com.yeskery.nut.annotation.web.PathVariable;
import com.yeskery.nut.annotation.web.RequestMapping;
import com.yeskery.nut.core.*;
import com.yeskery.nut.http.BasicMultipartFile;
import com.yeskery.nut.http.FormDataRequest;
import com.yeskery.nut.util.MediaTypeUtils;
import com.yeskery.nut.util.StringUtils;
import com.yeskery.nut.webhook.biz.ServerBiz;
import com.yeskery.nut.webhook.biz.WebHookBiz;
import com.yeskery.nut.webhook.domain.RequestInformation;

import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * webhook 页面controller
 * @author sprout
 * @version 1.0
 * 2023-06-03 16:50
 */
@Controller
public class WebHookController {

    private static final int DEFAULT_MAX_BYTE_LENGTH = 65536;

    private static final String AT = "@";

    private static final String NEW_LINE_SYMBOL = "\r\n";

    @Value("${server.web.protocol:http}")
    private String webProtocol;

    @Value("${server.ws.protocol:ws}")
    private String wsProtocol;

    @Autowired
    private WebHookBiz webHookBiz;

    @Autowired
    private ServerBiz serverBiz;

    /**
     * 首页页面
     * @param session
     * @param model
     * @return
     */
    @GetMapping("/")
    public String index(Session session, Model model) {
        String sessionId = session.getSessionId();
        String serverUrl = serverBiz.getServerUrl(null);
        model.addAttribute("wsServerUrl", wsProtocol + "://" + serverUrl);
        model.addAttribute("webServerUrl", webProtocol + "://" + serverUrl + "/webhook/session/");
        model.addAttribute("sessionId", sessionId);
        webHookBiz.beforeRegister(sessionId);
        return "index";
    }

    /**
     * 收集请求信息的controller
     * @param sessionId
     */
    @RequestMapping(value = "/webhook/session/{sessionId}", method = Method.ALL)
    public void webhook(@PathVariable("sessionId") String sessionId, Request request) {
        com.yeskery.nut.websocket.Session session = webHookBiz.getSession(sessionId);
        if (session == null) {
            throw new ResponseNutException(ResponseCode.NOT_FOUND.getDescription(), ResponseCode.NOT_FOUND);
        }
        RequestInformation requestInformation = new RequestInformation();
        String host = request.getHeader("Host");
        requestInformation.setUrl(webProtocol + "://" + serverBiz.getServerUrl(host) + request.getOriginalPath());
        requestInformation.setMethod(request.getMethod().name());
        requestInformation.setRemoteIp(request.getRemoteAddress());
        requestInformation.setRemotePort(request.getRemotePort());
        Map<String, List<String>> queryParametersMap = request.getQueryParametersMap();
        requestInformation.setQueryParmas(queryParametersMap.entrySet().stream()
                .map(e -> new KeyAndValue<>(e.getKey(), String.join(";", e.getValue()))).collect(Collectors.toList()));
        requestInformation.setRequestParmas(request.getParametersMap().entrySet().stream()
                .filter(e -> !queryParametersMap.containsKey(e.getKey()))
                .map(e -> {
                    StringBuilder result = new StringBuilder();
                    for (String value : e.getValue()) {
                        if (value.startsWith(BasicMultipartFile.class.getName() + AT)) {
                            result.append("File(The detail file information in [Request Body]);");
                        } else {
                            result.append(value).append(";");
                        }
                    }
                    if (result.charAt(result.length() - 1) == ';') {
                        result.deleteCharAt(result.length() - 1);
                    }
                    return new KeyAndValue<>(e.getKey(), result.toString());
                }).collect(Collectors.toList()));
        requestInformation.setHeaders(request.getHeaders().entrySet().stream()
                .map(e -> new KeyAndValue<>(e.getKey(), String.join(";", e.getValue()))).collect(Collectors.toList()));
        requestInformation.setContentType(request.getHeader(HttpHeader.CONTENT_TYPE));
        if (request instanceof FormDataRequest) {
            Map<String, List<Object>> formParametersMap = ((FormDataRequest) request).getFormParametersMap();
            List<Map.Entry<String, List<Object>>> entries = formParametersMap.entrySet().stream().sorted((e1, e2) -> {
                List<Object> value1 = e1.getValue();
                List<Object> value2 = e2.getValue();
                int v1 = value1 == null || value1.isEmpty() ? 0 : (value1.get(0) instanceof MultipartFile) ? 1 : 0;
                int v2 = value2 == null || value2.isEmpty() ? 0 : (value2.get(0) instanceof MultipartFile) ? 1 : 0;
                return v1 == v2 ? e1.getKey().compareTo(e2.getKey()) : Integer.compare(v1, v2);
            }).collect(Collectors.toList());
            StringBuilder stringBuilder = new StringBuilder();
            for (Map.Entry<String, List<Object>> entry : entries) {
                for (int i = 0; i < entry.getValue().size(); i++) {
                    Object value = entry.getValue().get(i);
                    if (value instanceof MultipartFile) {
                        if (i == 0) {
                            stringBuilder.append(entry.getKey()).append("(File): ");
                        }
                        MultipartFile multipartFile = (MultipartFile) value;
                        stringBuilder.append("FileName=").append(multipartFile.getFileName())
                                .append("; ContentType=").append(multipartFile.getContentType()).append(NEW_LINE_SYMBOL)
                                .append("File Data(Base64): ").append(getBase64FormatBodyBytes(multipartFile.getData())).append(NEW_LINE_SYMBOL);
                    } else {
                        if (i == 0) {
                            stringBuilder.append(entry.getKey()).append(": ");
                        }
                        stringBuilder.append(new String((byte[]) value, StandardCharsets.UTF_8)).append(";");
                    }
                }
                if (stringBuilder.charAt(stringBuilder.length() - 1) == ';') {
                    stringBuilder.deleteCharAt(stringBuilder.length() - 1);
                }
                if (!NEW_LINE_SYMBOL.equals(stringBuilder.substring(stringBuilder.length() -2))) {
                    stringBuilder.append(NEW_LINE_SYMBOL);
                }
            }
            String result = stringBuilder.toString();
            if (NEW_LINE_SYMBOL.equals(stringBuilder.substring(stringBuilder.length() -2))) {
                result = result.substring(0, stringBuilder.length() - 2);
            }
            requestInformation.setContent(result);
        } else {
            if (!StringUtils.isEmpty(requestInformation.getContentType()) &&
                    MediaTypeUtils.isBinaryMediaType(MediaType.getMediaTypeByValue(requestInformation.getContentType()))) {
                String content = "ContentType=" + requestInformation.getContentType() + NEW_LINE_SYMBOL +
                        "File Data(Base64): " + getBase64FormatBodyBytes(request.getBody());
                requestInformation.setContent(content);
            } else {
                requestInformation.setContent(new String(request.getBody(), StandardCharsets.UTF_8));
            }
        }
        session.sendText(JSONObject.toJSONString(requestInformation));
    }

    /**
     * 将请求体bytes转换为64格式，并忽略过长的字节数组
     * @param bytes
     * @return
     */
    private String getBase64FormatBodyBytes(byte[] bytes) {
        if (bytes == null || bytes.length == 0) {
            return "";
        }
        if (bytes.length > DEFAULT_MAX_BYTE_LENGTH) {
            byte[] newBytes = new byte[DEFAULT_MAX_BYTE_LENGTH];
            System.arraycopy(bytes, 0, newBytes, 0, DEFAULT_MAX_BYTE_LENGTH);
            return Base64.getEncoder().encodeToString(newBytes) + " (... Ignore " + (bytes.length - DEFAULT_MAX_BYTE_LENGTH) + " Bytes)";
        } else {
            return Base64.getEncoder().encodeToString(bytes);
        }
    }
}
